/*
 *
 *    Copyright (c) 2022 Project CHIP Authors
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
#pragma once

#include <app-common/zap-generated/cluster-enums.h>
#include <app/icd/server/ICDServerConfig.h>

#include <array>
#include <cmsis_os2.h>
#include <lib/support/BitFlags.h>
#include <lib/support/Span.h>
#include <platform/silabs/wifi/WifiStateProvider.h>
#include <platform/silabs/wifi/icd/PowerSaveInterface.h>
#include <platform/silabs/wifi/wfx_msgs.h>
#include <sl_cmsis_os2_common.h>
#include <sl_status.h>

#if (SLI_SI91X_MCU_INTERFACE | EXP_BOARD)
#include "rsi_common_apis.h"
#include "sl_si91x_types.h"
#include "sl_wifi_constants.h"
#include "sl_wifi_device.h"
#endif // (SLI_SI91X_MCU_INTERFACE | EXP_BOARD)

/* Updated constants */

constexpr size_t kWifiMacAddressLength = 6;

/* Defines to update */

// TODO: Not sure why the pass key max length differs for the 917 SoC & NCP
#if (SLI_SI91X_MCU_INTERFACE | EXP_BOARD)
// MAX PASSKEY LENGTH including NULL character
#define WFX_MAX_PASSKEY_LENGTH (SL_WIFI_MAX_PSK_LENGTH)
#else
// MAX PASSKEY LENGTH including NULL character
#define WFX_MAX_PASSKEY_LENGTH (64)
#endif // (SLI_SI91X_MCU_INTERFACE  | EXP_BOARD)

// MAX SSID LENGTH excluding NULL character
#define WFX_MAX_SSID_LENGTH (32)
#define MAX_JOIN_RETRIES_COUNT (5)

/* Note that these are same as RSI_security */
typedef enum
{
    WFX_SEC_UNSPECIFIED    = 0,
    WFX_SEC_NONE           = 1,
    WFX_SEC_WEP            = 2,
    WFX_SEC_WPA            = 3,
    WFX_SEC_WPA2           = 4,
    WFX_SEC_WPA3           = 5,
    WFX_SEC_WPA_WPA2_MIXED = 6,
} wfx_sec_t;

typedef struct wfx_wifi_scan_result
{
    uint8_t ssid[WFX_MAX_SSID_LENGTH]; // excludes null-character
    size_t ssid_length;
    wfx_sec_t security;
    uint8_t bssid[kWifiMacAddressLength];
    uint8_t chan;
    int16_t rssi; /* I suspect this is in dBm - so signed */
    chip::app::Clusters::NetworkCommissioning::WiFiBandEnum wiFiBand;
} wfx_wifi_scan_result_t;
using ScanCallback = void (*)(wfx_wifi_scan_result_t *);

typedef struct wfx_wifi_scan_ext
{
    uint32_t beacon_lost_count;
    uint32_t beacon_rx_count;
    uint32_t mcast_rx_count;
    uint32_t mcast_tx_count;
    uint32_t ucast_rx_count;
    uint32_t ucast_tx_count;
    uint32_t overrun_count;
} wfx_wifi_scan_ext_t;

#ifdef SL_MATTER_SIWX_WIFI_ENABLE
/*
 * This Sh%t is here to support WFXUtils - and the Matter stuff that uses it
 * We took it from the SDK (for WF200)
 */
typedef enum
{
    SL_WFX_STA_INTERFACE    = 0, ///< Interface 0, linked to the station
    SL_WFX_SOFTAP_INTERFACE = 1, ///< Interface 1, linked to the softap
} sl_wfx_interface_t;
#endif

/* Updated section */

namespace chip {
namespace DeviceLayer {
namespace Silabs {

/**
 * @brief Public Interface for the Wi-Fi platform APIs
 *
 */
class WifiInterface : public WifiStateProvider, public PowerSaveInterface
{
public:
    enum class WifiEvent : uint8_t
    {
        kStartUp      = 0,
        kConnect      = 1,
        kDisconnect   = 2,
        kScanComplete = 3,
        kGotIPv4      = 4,
        kGotIPv6      = 5,
        kLostIP       = 6,
    };

    enum class WifiState : uint16_t
    {
        kStationInit        = (1 << 0),
        kAPReady            = (1 << 1),
        kStationProvisioned = (1 << 2),
        kStationConnecting  = (1 << 3),
        kStationConnected   = (1 << 4),
        kStationDhcpDone    = (1 << 6), /* Requested to do DHCP after conn */
        kStationMode        = (1 << 7), /* Enable Station Mode */
        kAPMode             = (1 << 8), /* Enable AP Mode */
        kStationReady       = (kStationConnected | kStationDhcpDone),
        kStationStarted     = (1 << 9),
        kScanStarted        = (1 << 10), /* Scan Started */
    };

    enum class WifiDisconnectionReasons : uint16_t // using uint16 to match current structure during the transition
    {
        kUnknownError      = 1, // Disconnation due to an internal error
        kAccessPointLost   = 2, // Device did not receive AP beacon too many times
        kAccessPoint       = 3, // AP disconnected the device
        kApplication       = 4, // Application requested disconnection
        kWPACouterMeasures = 5, // WPA contermeasures triggered a disconnection
    };

    // TODO: Figure out if we need this structure. We have different strcutures for the same use
    struct WifiCredentials
    {
        WifiCredentials() { Clear(); }

        uint8_t ssid[WFX_MAX_SSID_LENGTH]       = { 0 };
        size_t ssidLength                       = 0;
        uint8_t passkey[WFX_MAX_PASSKEY_LENGTH] = { 0 };
        size_t passkeyLength                    = 0;
        wfx_sec_t security                      = WFX_SEC_UNSPECIFIED;

        WifiCredentials & operator=(const WifiCredentials & other)
        {
            if (this != &other)
            {
                memcpy(ssid, other.ssid, WFX_MAX_SSID_LENGTH);
                ssidLength = other.ssidLength;
                memcpy(passkey, other.passkey, WFX_MAX_PASSKEY_LENGTH);
                passkeyLength = other.passkeyLength;
                security      = other.security;
            }
            return *this;
        }

        void Clear()
        {
            memset(ssid, 0, WFX_MAX_SSID_LENGTH);
            ssidLength = 0;
            memset(passkey, 0, WFX_MAX_PASSKEY_LENGTH);
            passkeyLength = 0;
            security      = WFX_SEC_UNSPECIFIED;
        }
    };

    using MacAddress = std::array<uint8_t, kWifiMacAddressLength>;

    virtual ~WifiInterface() = default;

    /**
     * @brief Returns the singleton instance of the WiFi interface
     *
     *  @note This function needs to be implemented in the child classes sources file
     *
     * @return WifiInterface&
     */
    static WifiInterface & GetInstance();

    /**
     * @brief Function initalizes the WiFi module before starting the WiFi task.
     *
     * @return CHIP_ERROR CHIP_NO_ERROR, if the initialization succeeded
     *                    CHIP_ERROR_INTERNAL, if sequence failed due to internal API error
     *                    CHIP_ERROR_NO_MEMORY, if sequence failed due to unavaliablility of memory
     */

    virtual CHIP_ERROR InitWiFiStack(void) = 0;

    /**
     * @brief Returns the provide interfaces MAC address
     *        Valid buffer large enough for the MAC address must be provided to the function
     *
     * @param[in] interface SL_WFX_STA_INTERFACE or SL_WFX_SOFTAP_INTERFACE.
     *                      If soft AP is not enabled, the interface is ignored and the function always returns the Station MAC
     *                      address
     * @param[out] addr     Interface MAC addres
     *
     * @return CHIP_ERROR CHIP_NO_ERROR on success
     *                    CHIP_ERROR_BUFFER_TOO_SMALL if the provided ByteSpan size is too small
     *
     */
    virtual CHIP_ERROR GetMacAddress(sl_wfx_interface_t interface, chip::MutableByteSpan & addr) = 0;

    /**
     * @brief Triggers a network scan
     *        The function is asynchronous and the result is provided via the callback.
     *
     * @param ssid The SSID to scan for. If empty, all networks are scanned
     * @param callback The callback to be called when the scan is complete. Cannot be nullptr.
     *                 The callback is called asynchrounously.
     *
     * @return CHIP_ERROR CHIP_NO_ERROR if the network scan was successfully started
     *                    CHIP_INVALID_ARGUMENT if the callback is nullptr
     *                    CHIP_ERROR_IN_PROGRESS, if there is already a network scan in progress
     *                    CHIP_ERROR_INVALID_STRING_LENGTH, if there SSID length exceeds handled limit
     *                    other, if there is a platform error when starting the scan
     */
    virtual CHIP_ERROR StartNetworkScan(chip::ByteSpan ssid, ScanCallback callback) = 0;

    /**
     * @brief Creates and starts the WiFi task that processes Wifi events and operations
     *
     * @return CHIP_ERROR CHIP_NO_ERROR if the task was successfully started and initialized
     *         CHIP_ERROR_NO_MEMORY if the task failed to be created
     *         CHIP_ERROR_INTERNAL if software or hardware initialization failed
     */
    virtual CHIP_ERROR StartWifiTask() = 0;

    /**
     * @brief Configures the Wi-Fi devices as a Wi-Fi station
     */
    virtual void ConfigureStationMode() = 0;

    /**
     * @brief Triggers the device to disconnect from the connected Wi-Fi network
     *
     * @note The disconnection is not immediate. It can take a certain amount of time for the device to be in a disconnected state
     * once the function is called. When the function returns, the device might not have yet disconnected from the Wi-Fi network.
     *
     * @return CHIP_ERROR CHIP_NO_ERROR, disconnection request was succesfully triggered
     *         otherwise, CHIP_ERROR_INTERNAL
     */
    virtual CHIP_ERROR TriggerDisconnection() = 0;

    /**
     * @brief Gets the connected access point information.
     *        See @wfx_wifi_scan_result_t for the information that is returned by the function.
     *
     * @param[out] info AP information
     *
     * @return CHIP_ERROR CHIP_NO_ERROR, device has succesfully pulled all the AP information
     *                    CHIP_ERROR_INTERNAL, otherwise. If the function returns an error, the data in ap cannot be used.
     */
    virtual CHIP_ERROR GetAccessPointInfo(wfx_wifi_scan_result_t & info) = 0;

    /**
     * @brief Gets the connected access point extended information.
     *        See @wfx_wifi_scan_ext_t for the information that is returned by the information
     *
     * @param[out] info AP extended information
     *
     * @return CHIP_ERROR CHIP_NO_ERROR, device has succesfully pulled all the AP information
     *                    CHIP_ERROR_INTERNAL, otherwise. If the function returns an error, the data in ap cannot be used.
     */
    virtual CHIP_ERROR GetAccessPointExtendedInfo(wfx_wifi_scan_ext_t & info) = 0;

    /**
     * @brief Function resets the BeaconLostCount, BeaconRxCount, PacketMulticastRxCount, PacketMulticastTxCount,
     * PacketUnicastRxCount, PacketUnicastTxCount back to 0
     *
     * @return CHIP_ERROR CHIP_NO_ERROR, the counters were succesfully reset to 0.
     *                    CHIP_ERROR_INTERNAL, if there was an error when resetting the counter values
     */
    virtual CHIP_ERROR ResetCounters() = 0;

    /**
     * @brief Clears the stored Wi-Fi crendetials stored in RAM only
     */
    virtual void ClearWifiCredentials() = 0;

    /**
     * @brief Stores the Wi-Fi credentials
     *
     * @note Function does not validate if the device already has Wi-Fi credentials.
     *       It is the responsibility of the caller to ensuret that.
     *       The function will overwrite any existing Wi-Fi credentials.
     *
     * @param[in] credentials
     */
    virtual void SetWifiCredentials(const WifiCredentials & credentials) = 0;

    /**
     * @brief Returns the configured Wi-Fi credentials
     *
     * @param[out] credentials stored wifi crendetials
     *
     * @return CHIP_ERROR CHIP_ERROR_INCORRECT_STATE, if the device does not have any set credentials
     *                    CHIP_NO_ERROR, otherwise
     */
    virtual CHIP_ERROR GetWifiCredentials(WifiCredentials & credentials) = 0;

    /**
     * @brief Triggers a connection attempt the Access Point who's crendetials match the ones store with the SetWifiCredentials API.
     *        The function triggers an async connection attempt. The upper layers are notified trought a platform event if the
     *        connection attempt was successful or not.
     *
     *        The returned error code only indicates if the connection attempt was triggered or not.
     *
     * @return CHIP_ERROR CHIP_NO_ERROR, the connection attempt was succesfully triggered
     *                    CHIP_ERROR_INCORRECT_STATE, the Wi-Fi station does not have any Wi-Fi credentials
     *                    CHIP_ERROR_INVALID_ARGUMENT, the provisionned crendetials do not match the Wi-Fi station requirements
     *                    CHIP_ERROR_INTERNAL, otherwise
     */
    virtual CHIP_ERROR ConnectToAccessPoint(void) = 0;

    /**
     * @brief Cancels the on-going network scan operation.
     *        If one isn't in-progress, function doesn't do anything
     */
    virtual void CancelScanNetworks() = 0;

    /**
     *  @brief Provide all the frequency bands supported by the Wi-Fi interface.
     *
     *  The default implementation returns the 2.4 GHz band support.
     *
     *  @return a bitmask of supported Wi-Fi bands where each bit is associated with a WiFiBandEnum value.
     */
    virtual uint32_t GetSupportedWiFiBandsMask() const
    {
        // Default to 2.4G support only
        return static_cast<uint32_t>(1UL << chip::to_underlying(chip::app::Clusters::NetworkCommissioning::WiFiBandEnum::k2g4));
    }

    /**
     * @brief Function resets reconnection attempt interval back to the minimum value
     */
    void ResetConnectionRetryInterval();

protected:
    /**
     * @brief Function notifies the PlatformManager that an IPv6 event occured on the WiFi interface.
     *
     * @param gotIPv6Addr true, got an IPv6 address
     *                    false, lost or wasn't able to get an IPv6 address
     */
    void NotifyIPv6Change(bool gotIPv6Addr);

#if CHIP_DEVICE_CONFIG_ENABLE_IPV4
    /**
     * @brief Updates the IPv4 address in the Wi-Fi interface and notifies the application layer about the new IP address.
     *
     * @param[in] ip New IPv4 address
     */
    void GotIPv4Address(uint32_t ip);
    /**
     * @brief Function notifies the PlatformManager that an IPv4 event occured on the WiFi interface.
     *
     * @param gotIPv4Addr true, got an IPv4 address
     *                    false, lost or wasn't able to get an IPv4 address
     */
    void NotifyIPv4Change(bool gotIPv4Addr);
#endif /* CHIP_DEVICE_CONFIG_ENABLE_IPV4 */

    /**
     * @brief Function notifies the PlatformManager that a disconnection event occurred
     *
     * @param reason reason for the disconnection
     */
    void NotifyDisconnection(WifiDisconnectionReasons reason);

    /**
     * @brief Function notifies the PlatformManager that a connection event occurred
     *
     * @param[in] ap pointer to the structure that contains the MAC address of the AP
     */
    void NotifyConnection(const MacAddress & ap);

    /**
     * @brief Function resets the IP notification states
     *
     */
    void ResetIPNotificationStates();

    /**
     * @brief Notifies upper-layers that Wi-Fi initialization has succesfully completed
     */
    void NotifyWifiTaskInitialized(void);

    /**
     * @brief Function schedules a reconnection attempt with the Access Point
     *
     * @note The retry interval increases exponentially with each attempt, starting from a minimum value and doubling each time,
     *       up to a maximum value. For example, if the initial retry interval is 1 second, the subsequent intervals will be 2
     * seconds, 4 seconds, 8 seconds, and so on, until the maximum retry interval is reached.
     */
    void ScheduleConnectionAttempt();

    bool mHasNotifiedIPv6 = false;
#if CHIP_DEVICE_CONFIG_ENABLE_IPV4
    bool mHasNotifiedIPv4 = false;
#endif // CHIP_DEVICE_CONFIG_ENABLE_IPV4

private:
    osTimerId_t mRetryTimer;
};

} // namespace Silabs
} // namespace DeviceLayer
} // namespace chip

// TODO: This structure can be split into members of the interfaces
// This needs to be after the class definition since it depends on class members
typedef struct wfx_rsi_s
{
    chip::BitFlags<chip::DeviceLayer::Silabs::WifiInterface::WifiState> dev_state;
    uint16_t ap_chan; /* The chan our STA is using	*/
    chip::DeviceLayer::Silabs::WifiInterface::WifiCredentials credentials;
    ScanCallback scan_cb;
#ifdef SL_WFX_CONFIG_SOFTAP
    chip::DeviceLayer::Silabs::WifiInterface::MacAddress softap_mac;
#endif
    chip::DeviceLayer::Silabs::WifiInterface::MacAddress sta_mac;
    chip::DeviceLayer::Silabs::WifiInterface::MacAddress ap_mac;   /* To which our STA is connected */
    chip::DeviceLayer::Silabs::WifiInterface::MacAddress ap_bssid; /* To which our STA is connected */
    uint8_t ip4_addr[4];                                           /* Not sure if this is enough */
} WfxRsi_t;
